package com.tbit.uqbike.client.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import com.alibaba.fastjson.JSON;
import com.tbit.uqbike.center.pojo.MachineStatus;
import com.tbit.uqbike.client.constant.*;
import com.tbit.uqbike.client.dao.core.AccountConfigExtDao;
import com.tbit.uqbike.client.dao.core.MachineFunctionDao;
import com.tbit.uqbike.client.dao.core.ParkPointDao;
import com.tbit.uqbike.client.dao.core.ParkPointVertDao;
import com.tbit.uqbike.client.dao.ter.TerAttrExDao;
import com.tbit.uqbike.client.exception.BaseException;
import com.tbit.uqbike.client.pojo.*;
import com.tbit.uqbike.client.pojo.newEntity.AccountConfigExt;
import com.tbit.uqbike.client.pojo.vo.MyLatLng;
import com.tbit.uqbike.client.service.*;
import com.tbit.uqbike.client.util.DateTimeUtil;
import com.tbit.uqbike.client.util.GeoUtil;
import com.tbit.uqbike.client.util.MapUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service("parkPointService")
@Slf4j
public class ParkPointServiceImpl implements ParkPointService {

    private static final String REFERENCE_DATE = "2020-10-01 00:00:00";
    /**
     * 根据新增站点时间判断误差范围 2020-10-01 之前的误差 30 米，之后误差 10 米
     */
    private static Date referenceDate = null;

    static {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            referenceDate = simpleDateFormat.parse(REFERENCE_DATE);
        } catch (ParseException e) {
            log.error("时间初始化失败");
        }
    }

    @Autowired
    private ParkPointDao parkPointDao;
    @Autowired
    private CalibrateService calibrateService;
    @Autowired
    private TerPositionService terPositionService;
    @Autowired
    private ParkMachineService parkMachineService;
    @Autowired
    private MachineStatusConsumerService machineStatusConsumerService;
    @Autowired
    private AccountConfigService accountConfigService;
    @Autowired
    private MachineFunctionDao machineFunctionDao;
    @Autowired
    private MachineService machineService;
    @Autowired
    private TerAttrExDao terAttrExDao;
    @Autowired
    private ParkPointVertDao parkPointVertDao;
    @Autowired
    private MachineStatusService machineStatusService;
    @Autowired
    private MachineCheckService machineCheckService;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private AccountConfigExtDao accountConfigExtDao;

    /**
     * @param parkAngle 平台角度
     * @param angle     终端角度
     * @param offset    误差角度
     * @return
     */
    public static double getAngle(double parkAngle, double angle, double offset) {
        double tmp = 0.0;
        if (360 - offset < parkAngle && parkAngle < 360 && angle > 0 && angle < offset) {
            if ((0 <= angle && angle <= (parkAngle + offset - 360))) {
                tmp = 360 - parkAngle + angle;
            }
            if ((parkAngle - offset <= angle && angle <= 360)) {
                tmp = Math.abs(parkAngle - angle);
            }
        } else if (360 - offset < angle && angle < 360 && parkAngle > 0 && parkAngle < offset) {
            tmp = 360 - angle + parkAngle;
        } else {
            tmp = Math.abs(parkAngle - angle);
        }
        return tmp;
    }

    @Override
    public List<ParkPoint> getByAccountId(Integer accountId) {
        return parkPointDao.getByAccountId(accountId);
    }

    @Override
    public List<ParkPoint> getByArea(Integer accountId, Double lo, Double la, Integer radius) {
        Double area = 0.00001D * radius;

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountId", accountId);
        params.put("minLo", lo - area);
        params.put("maxLo", lo + area);
        params.put("minLa", la - area);
        params.put("maxLa", la + area);

        return parkPointDao.getByArea(params);
    }

    @Override
    public List<ParkPoint> getAccountByArea(Integer accountId, Double lo, Double la, Integer radius) {
		/*Map<String,List<ParkPoint>> map = SystemData.parkPointMap;
		GeoHash geoHash = new GeoHash(GeoHashLevel.GHL_M_1200);
		*//**获取经纬度点附近9个区域*//*
		ArrayList<String> strings = geoHash.getArroundGeoHash(la,lo);
		List<ParkPoint> parkPoints = new ArrayList<>();
		for (String s:strings) {
			List<ParkPoint> list = map.get(s+accountId);
			if(list!=null&&list.size()>0){
				parkPoints.addAll(list);
			}
		}
		return  parkPoints;*/
        int finalRadius = ObjUtil.defaultIfNull(radius, () -> Optional.ofNullable(accountConfigExtDao.selectByAccountIdAndKey(accountId, AccountConfigExtConstant.PARK_POINT_SCOPE))
                .map(AccountConfigExt::getParamValue)
                .map(Integer::parseInt)
                .orElse(500));

        // 配置值为0表示查询整个运营区的数据
        if (finalRadius == 0) {
            return parkPointDao.getByAccountId(accountId);
        }

        Point[] rect = GeoUtil.getBoundingBox(lo, la, finalRadius);

        // 这里有更精细化的优化空间
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("minLo", rect[0].getX());
        params.put("minLa", rect[0].getY());
        params.put("maxLo", rect[1].getX());
        params.put("maxLa", rect[1].getY());
        return parkPointDao.getAccountByArea(params);
    }

    @Override
    public List<ParkPoint> getNearParkPoint(User user, Integer accountId, Double lo, Double la, Integer radius, Integer mapType) {
        List<ParkPoint> parkPoints = new ArrayList<ParkPoint>();

        List<ParkPoint> parkPointDB;
        AccountConfig accountConfig = accountConfigService.getByAccountId(accountId);
        /**打开了大站点模式*/
        if (accountConfig != null && accountConfig.getBigParkPoint() == 1) {
            parkPointDB = getByAccountId(accountId);
        } else {
            parkPointDB = getAccountByArea(accountId, lo, la, radius);
        }
        if (parkPointDB != null) {
            parkPoints.addAll(parkPointDB);
        }

        if (parkPoints.size() > 0) {
            /**校准停车点经纬度*/
            if (parkPoints.size() > 0) {
                /**校准停车点经纬度*/
                for (ParkPoint parkPoint : parkPoints) {
                    calibrateService.calParkPoint(parkPoint, SysConstant.ORIGINAL, mapType);
                    Double distince = MapUtils.GetDistance(lo, la, parkPoint.getLo(), parkPoint.getLa());
                    parkPoint.setDistance(distince);
                }
            }
        }
        /**通过距离排序*/
        parkPoints.sort((x, y) -> Double.compare(x.getDistance(), y.getDistance()));

		/*if (parkPoints.size() > 0) {
			for (ParkPoint p : parkPoints) {
				Integer canBorrowNum = parkMachineService.getParkPointCanBorrowNum(p.getParkPointId());
				if (canBorrowNum > 0) {
					p.setCanBorrowNum(canBorrowNum);
				}
			}
		}*/

        return parkPoints;
    }

    @Override
    public List<MachineStatus> getStatusByParkPoint(Integer parkPointId) {
        List<MachineStatus> machineStatuses = new ArrayList<MachineStatus>();
        /**查询停车点数据*/
        ParkPoint parkPoint = getByParkPointId(parkPointId);
        List<TerPosition> terPositions = filterMachine(parkPoint);

        //增加从dubbo中获取电池信息
        List<Integer> machineIdList = new ArrayList<>();
        for (TerPosition tp : terPositions) {
            machineIdList.add(tp.getMachineId());
        }
        machineStatuses = machineStatusConsumerService.getByMachineIds(machineIdList);

        return machineStatuses;
    }

    /**
     * 筛选停车点内可用数据
     *
     * @param parkPoint
     * @return
     */
    private List<TerPosition> filterMachine(ParkPoint parkPoint) {
        List<TerPosition> filterTerPosition = new ArrayList<TerPosition>();
        Map<String, Integer> machineNOMachineId = new HashMap<>();
        /**查询停车点内车辆*/
        List<ParkMachine> parkMachines = parkMachineService.getByParkPointId(parkPoint.getParkPointId());
        if (parkMachines.size() > 0) {
            List<String> machineNOs = new ArrayList<String>();
            for (ParkMachine p : parkMachines) {
                machineNOs.add(p.getMachineNO());
                machineNOMachineId.put(p.getMachineNO(), p.getMachineId());
            }

            /**查询位置*/
            List<TerPosition> terPositions = terPositionService.getByMachineNOBatch(machineNOs);
            for (TerPosition t : terPositions) {
                t.setMachineId(machineNOMachineId.get(t.getMachineNO()));
                filterTerPosition.add(t);
            }

            parkPoint.setCanBorrowNum(filterTerPosition.size());
        }

        return filterTerPosition;
    }

    @Override
    public ParkPoint getByParkPointId(Integer parkPointId) {
        return parkPointDao.getByParkPointId(parkPointId);
    }

    @Override
    public List<ParkPoint> getReturnByArea(Integer accountId, Double lo, Double la, Integer radius) {
        Double area = 0.00001D * radius;

        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountId", accountId);
        params.put("minLo", lo - area);
        params.put("maxLo", lo + area);
        params.put("minLa", la - area);
        params.put("maxLa", la + area);

        return parkPointDao.getReturnByArea(params);
    }

    /**
     * 判断用户是否是在站点还车
     *
     * @param accountId
     * @param point
     * @param machineId
     * @return
     */
    @Override
    public Integer checkInParkPoint(Integer accountId, Point point, Integer machineId, Map<String, Double> distanceMap) {
        // 附近站点
        Integer parkPointId = null;
        List<ParkPoint> parkPoints = new ArrayList<>();
        List<ParkPoint> parkPointDB;
        AccountConfig accountConfig = accountConfigService.getByAccountId(accountId);
        // 是否打开了大站点模式
        if (accountConfig != null && accountConfig.getBigParkPoint() == 1) {
            parkPointDB = getByAccountId(accountId);
        } else {
            parkPointDB = getAccountByArea(accountId, point.getX(), point.getY(), 500);
        }

        if (parkPointDB != null) {
            parkPoints.addAll(parkPointDB);
        }

        distanceMap = Optional.ofNullable(distanceMap).orElse(new HashMap<>());
        boolean isHigh = Optional.ofNullable(distanceMap.get("posType")).map(x -> x.intValue() == 2).orElse(false);
        // 高精度的车辆如果上传了高精度定位，则优先使用高精度站点还车
        if (isHigh && !parkPoints.isEmpty()) {
            parkPoints = parkPoints.stream()
                    .sorted(Comparator.comparingInt(ParkPoint::getSiteType).reversed())
                    .collect(Collectors.toList());
        }

        /*
        判断车辆是否在站点要求的经纬度内
         */
        for (ParkPoint parkPoint : parkPoints) {
            Double distince;
            distanceMap.put("parkPointId", parkPoint.getParkPointId().doubleValue());
            /*
            检查车辆是否在多边形站点中，否则计算车辆到站点的最短距离
             */
            if (parkPoint.getParkPointType() != null && parkPoint.getParkPointType().equals(1)) {
                // 停车点是多边形
                List<Point> points = GeoUtil.getPoints(parkPoint.getPoints());
                // 判断是否在多边形内
                if (GeoUtil.IsPtInPoly(point, points)) {
                    parkPointId = parkPoint.getParkPointId();
                    break;
                }
                distince = GeoUtil.getDistance(point, points);
            } else {
                // 默认停车点是圆形
                distince = MapUtils.GetDistance(point.getX(), point.getY(), parkPoint.getLo(), parkPoint.getLa());
            }
            distanceMap.put("returnDistance", distince);

            /**根据条件设置允许的误差范围  最终误差范围*/
            Integer allowRange;
            /**站点误差范围*/
            Integer parkPointAllowRange = 0;
            /**最小误差范围*/
            Integer min = (parkPoint.getUpdateTime() != null && parkPoint.getUpdateTime().after(referenceDate)) ? 10 : 30;

            /**同时配置整体站点误差范围和单个站点误差范围，优先使用单个站点误差范围*/
            if (parkPoint.getAllowRange() != -1){
                parkPointAllowRange = parkPoint.getAllowRange();
            }else {
                AccountConfigExt allowRangeConfigExt = cacheService.getAccountConfigExtByKey(accountId, AccountConfigExtConstant.ALL_PARK_POINT_ALLOW_RANGE);
                if (allowRangeConfigExt != null){
                    parkPointAllowRange = Integer.parseInt(allowRangeConfigExt.getParamValue());
                }
            }

            // 取较大误差值
            if (parkPoint.getParkPointType() != null && parkPoint.getParkPointType().equals(1)) {
                allowRange = parkPointAllowRange < min ? min : parkPointAllowRange;
            } else {
                allowRange = parkPointAllowRange < 30 ? 30 : parkPointAllowRange;
            }

            /*
            兼容高精度定位功能
             */
            Map<String, Object> param = new HashMap<>();
            param.put("machineId", machineId);
            param.put("functionType", MachineStateConstant.MACHINE_TYPE_HIGH);
            MachineFunction machineFunction = machineFunctionDao.getByMachineId(param);
            //3.7改动,高精度站点+高精度车辆+中控上报高精度定位才走高精度判断,否则走正常流程
            if (machineFunction != null && machineFunction.getFunctionValue() == 1
                    && ParkPointConstant.HIGH_PRECISION_SITE.equals(parkPoint.getSiteType()) && isHigh) {
                allowRange = parkPoint.getHighPrecisionAllowRange() == null ? 0 : parkPoint.getHighPrecisionAllowRange();
                //根据厘米来算
                distince *= 100;
            }

            if (distince <= allowRange) {
                parkPointId = parkPoint.getParkPointId();
                log.info("设备id={},距离站点{}={}，经纬度={},{}", machineId, parkPointId, distince, point.getX(), point.getY());
                break;
            }
        }

        /*
        兼容 rfid 等辅助还车功能
         */
        AccountConfigExt configExt = cacheService.getAccountConfigExtByKey(accountId, AccountConfigExtConstant.ACCOUNTCONFIG_RFID);
        if (configExt != null) {
            // 格式为 1,3 代表打开，还车次数 3 次
            String status = configExt.getParamValue().split(",")[0];
            if ("1".equals(status)) {
                /*
                检测到 rfid，rfid 有绑定站点，以 rfid 站点为主
                 */
                Machine machine = machineService.getByMachineId(machineId);
                String value = redisService.get(FunctionConstant.RFID + machine.getMachineNO());
                if(value!=null) {
                    Integer parkPointIdRfid = machineCheckService.isInRfid(value, accountId, machine.getMachineNO(), point);
                    if (parkPointIdRfid != null) {
                        parkPointId = parkPointIdRfid;
                    }
                }
            }
        }

        return parkPointId;
    }

    @Override
    public String getMaintainPhone(Integer parkPointId) {
        return parkPointDao.getMaintainPhone(parkPointId);
    }

    @Override
    public ParkPoint getPark(Integer accountId, Point point,
                             Integer mapType) {
        List<ParkPoint> parkPoints = new ArrayList<ParkPoint>();
        List<ParkPoint> parkPointDB;
        AccountConfig accountConfig = accountConfigService.getByAccountId(accountId);
        /**打开了大站点模式*/
        if (accountConfig != null && accountConfig.getBigParkPoint() == 1) {
            parkPointDB = getByAccountId(accountId);
        } else {
            parkPointDB = getAccountByArea(accountId, point.getX(), point.getY(), null);
        }
        if (CollectionUtil.isEmpty(parkPointDB)) {
            parkPointDB = getAccountByArea(accountId, point.getX(), point.getY(), 2000);
        }

        if (parkPointDB != null) {
            parkPoints.addAll(parkPointDB);
        }
        log.info("平台id为{},获取用户手机的经纬度位置为{},获取到的站点列表为{}", accountId, JSON.toJSONString(point), JSON.toJSONString(parkPoints));
        /**中间距离值*/
        Double tmp = null;
        ParkPoint park = null;
        for (ParkPoint parkPoint : parkPoints) {
            // （站点满庄） 计算站点容量 与 实际站点内设备数量 关系，剔除已满站点
            Integer capacity = Objects.equals(parkPoint.getCapacity(), 0) ? Integer.MAX_VALUE : parkPoint.getCapacity();
            Integer parkPointCount = parkMachineService.getParkPointCount(parkPoint.getParkPointId());
            log.info("当前站点容量为{}，当前站点内设备数量为{}", capacity, parkPointCount);
            if (parkPointCount >= capacity) {
                log.info("站点内车辆大于等于站点容量，站点{}被剔除", JSON.toJSONString(park));
                continue;
            }

            Double distince;
            if (parkPoint.getParkPointType() != null && parkPoint.getParkPointType().equals(1)) {
                List<Point> points = GeoUtil.getPoints(parkPoint.getPoints());
                distince = GeoUtil.getDistance(point, points);
            } else {
                /** 默认停车点是圆形 */
                distince = MapUtils.GetDistance(point.getX(), point.getY(), parkPoint.getLo(), parkPoint.getLa());
            }
            //LogUtil.info("["+parkPoint.getName()+"]-->["+distince.intValue()+"]");
            if (tmp == null) {
                tmp = distince;
                park = parkPoint;

            } else {
                if (distince < tmp) {
                    tmp = distince;
                    park = parkPoint;
                }
            }
        }
        /**格式化校准*/
        if (park != null) {
            calibrateService.calParkPoint(park, SysConstant.ORIGINAL, mapType);
        }
        return park;
    }

    @Override
    public ParkPointVo getVert(String userCode, Integer parkPointId, Double lon, Double lat) {
        Machine machine = machineService.getByUserCode(userCode);
        if (machine == null) {
            throw new BaseException("-30005", "设备不存在");
        }
        ParkPointVo parkPointVo = new ParkPointVo();
        AccountConfig accountConfig = accountConfigService.getByAccountId(machine.getAccountId());
        if (accountConfig != null) {
            parkPointVo.setOffset(accountConfig.getOffsetAngle());
        }
        MachineStatus machineStatus = machineStatusService.getByMachineNO(machine.getMachineNO(),
                MapConstant.MAP_ORI);
        if (machineStatus == null || (machineStatus.getLon() == null || machineStatus.getLat() == null)) {
            throw new BaseException("-3029", "故障车辆，车辆还没有上线");
        }
        Point point = new Point(machineStatus.getLon(), machineStatus.getLat());
        /**判断车辆位置是否在站点*/
        if (parkPointId == null) {
            parkPointId = checkInParkPoint(machine.getAccountId(), point, machine.getMachineId(), null);
        }
        /**车辆位置不在，在判断手机位置*/
        if (parkPointId == null && lon != null && lat != null) {
            Point point1 = new Point(lon, lat);
            parkPointId = checkInParkPoint(machine.getAccountId(), point1, machine.getMachineId(), null);
        }
        if (parkPointId != null) {
            ParkPoint parkPoint = parkPointDao.getByParkPointId(parkPointId);

            BeanUtils.copyProperties(parkPoint, parkPointVo);
            parkPointVo.setMachineAngle(-1.0);
            Map<String, Object> attrMap = new HashMap<>();
            attrMap.put("machineNO", machine.getMachineNO());
            attrMap.put("name", "tLD");
            TerAttrEx terAttrEx = terAttrExDao.getByMachineNO(attrMap);
            if (terAttrEx != null && Integer.valueOf(terAttrEx.getValue()) >= 0 && Integer.valueOf(terAttrEx.getValue()) <= 3600) {
                parkPointVo.setDt(DateTimeUtil.DateTimeToString(terAttrEx.getDt()));
                parkPointVo.setMachineAngle(Double.valueOf(terAttrEx.getValue()) / 10);
            }
            ParkPointVert parkPointVert = parkPointVertDao.getByParkPointId(parkPointId);
            if (parkPointVert != null) {
                String[] strings = parkPointVert.getPoints().split(";");
                /**只取前两个点*/
                String[] strings1 = strings[0].split(",");
                String[] strings2 = strings[1].split(",");
                MyLatLng myLatLng = new MyLatLng(Double.parseDouble(strings1[0]), Double.parseDouble(strings1[1]));
                MyLatLng myLatLng1 = new MyLatLng(Double.parseDouble(strings2[0]), Double.parseDouble(strings2[1]));
                double tmp = GeoUtil.getAngle(myLatLng, myLatLng1);
                parkPointVo.setPtAngle(tmp);
                parkPointVo.setVertPointsC(calibrateService.calVert(parkPointVert.getPoints(), MapConstant.MAP_GOOGLE,
                        MapConstant.MAP_ORI));
                double offAngle = getAngle(tmp, parkPointVo.getMachineAngle(), parkPointVo.getOffset());
                parkPointVo.setOffAngle(offAngle);
            }
        } else {
            throw new BaseException("-3008", "请在停车点内还车");
        }

        return parkPointVo;
    }

    @Override
    public List<ParkPoint> getAll(String updateTime) {
        return parkPointDao.getAll(updateTime);
    }

    @Override
    public Integer getNumber() {
        return parkPointDao.getNumber();
    }


}  